package cc.shacocloud.mirage.utils.annotation;

import cc.shacocloud.mirage.utils.ClassUtil;
import cc.shacocloud.mirage.utils.reflection.ReflectUtil;
import org.jetbrains.annotations.NotNull;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.NoSuchElementException;


/**
 * 合成注解调用处理程序
 * <p>
 * 基于源注解生成代理对象以支持复杂逻辑使用
 *
 * @author 思追(shaco)
 */
public class CombinationAnnotationInvocationHandler<A extends Annotation> implements InvocationHandler {
    
    @NotNull
    private final CombinationAnnotationElement annotation;
    
    @NotNull
    private final Class<A> annotationType;
    
    @NotNull
    private final AttributeMethods attributes;
    
    public CombinationAnnotationInvocationHandler(@NotNull CombinationAnnotationElement annotation, @NotNull Class<A> annotationType) {
        this.annotation = annotation;
        this.annotationType = annotationType;
        this.attributes = AttributeMethods.forAnnotationType(annotationType);
    }
    
    /**
     * 创建注解的代理对象
     *
     * @param annotation 注解
     * @param <A>        注解泛型
     * @return 返回代理对象
     */
    @SuppressWarnings("unchecked")
    public static <A extends Annotation> @NotNull A createProxy(@NotNull CombinationAnnotationElement annotation,
                                                                @NotNull Class<A> annotationType) {
        ClassLoader classLoader = annotationType.getClassLoader();
        InvocationHandler handler = new CombinationAnnotationInvocationHandler<>(annotation, annotationType);
        Class<?>[] interfaces = new Class<?>[]{annotationType};
        return (A) Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        if (ReflectUtil.isEqualsMethod(method)) {
            return annotation.equals(args[0]);
        } else if (ReflectUtil.isHashCodeMethod(method)) {
            return annotation.hashCode();
        } else if (ReflectUtil.isToStringMethod(method)) {
            return annotation.toString();
        } else if (isAnnotationTypeMethod(method)) {
            return this.annotationType;
        } else if (this.attributes.indexOf(method.getName()) != -1) {
            return getAttributeValue(method);
        } else {
            throw new RuntimeException(String.format("合成注解类型 [%s] 不支持方法 [%s] 调用", this.annotationType, method));
        }
    }
    
    /**
     * 是否为注解类型方法
     */
    private boolean isAnnotationTypeMethod(@NotNull Method method) {
        return (method.getName().equals("annotationType") && method.getParameterCount() == 0);
    }
    
    /**
     * 获取属性值
     */
    private Object getAttributeValue(@NotNull Method method) {
        String attributeName = method.getName();
        Class<?> type = ClassUtil.resolvePrimitiveIfNecessary(method.getReturnType());
        Object value = this.annotation.getValue(annotationType, attributeName, type)
                .orElseThrow(() -> new NoSuchElementException("在组合注解元素 " + this.annotation.getType().getName() + "中找不到属性" + attributeName + "的值"));
        
        // 克隆非空数组，以便用户无法更改缓存中值的内容
        if (value.getClass().isArray() && Array.getLength(value) > 0) {
            value = cloneArray(value);
        }
        
        return value;
    }
    
    /**
     * 克隆提供的数组，确保保留原始组件类型
     */
    private Object cloneArray(Object array) {
        if (array instanceof boolean[]) {
            return ((boolean[]) array).clone();
        } else if (array instanceof byte[]) {
            return ((byte[]) array).clone();
        } else if (array instanceof char[]) {
            return ((char[]) array).clone();
        } else if (array instanceof double[]) {
            return ((double[]) array).clone();
        } else if (array instanceof float[]) {
            return ((float[]) array).clone();
        } else if (array instanceof int[]) {
            return ((int[]) array).clone();
        } else if (array instanceof long[]) {
            return ((long[]) array).clone();
        } else if (array instanceof short[]) {
            return ((short[]) array).clone();
        } else {
            return ((Object[]) array).clone();
        }
    }
}
